#include "ae.h"
#include "misc_utils.h"
#include "test.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>

typedef struct SOCK_CONNECTION_T
{
    LIST_NODE_T link;
    struct sockaddr_in target;
    int sock;
} SOCK_CONNECTION_T;

SOCK_CONNECTION_T* new_connection(struct sockaddr_in* target, int sock)
{
    SOCK_CONNECTION_T* cnxn = NEW(SOCK_CONNECTION_T);
    cnxn->target = *target;
    cnxn->sock = sock;
    return cnxn;
}

int on_timer(struct aeEventLoop *eventLoop, long long id, void *clientData)
{
    long data = (long)clientData;
    printf("Timer %lld expired, data=%ld\n", id, data);
    aeDeleteTimeEvent(eventLoop, id);
    if (data == 2)
    {
        printf("Stopping event loop\n");
        aeStop(eventLoop);
    }
    return 0;
}

void data_ready(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask)
{
    char buffer[1000];
    LIST_T* list = (LIST_T*)clientData;
    ssize_t len = recv(fd, buffer, sizeof(buffer), 0);
    (void)mask;
    buffer[len] = 0;    // as a test, I don't check array boundary
    printf("Got %ld bytes from %d: %s\n", len, fd, buffer);
    if (len == 0)
    {
        LIST_NODE_T* node;
        // we think this socket is closed by peer
        aeDeleteFileEvent(eventLoop, fd, AE_READABLE);
        close(fd);
        // delete this connection from list
        for (node = list->head; node != NULL; node = node->next)
        {
            SOCK_CONNECTION_T* cnxn = CONTAINER_PTR(SOCK_CONNECTION_T, link, node);
            if (cnxn->sock == fd)
            {
                printf("Closing socket %d\n", fd);
                list_remove(list, node);
                DELETE(cnxn);
                break;
            }
        }
    }
}

void accept_ready(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask)
{
    // todo accept
    struct sockaddr_in addr;
    LIST_T* list = (LIST_T*)clientData;
    socklen_t addr_len = sizeof(addr);
    int sock = accept(fd, (struct sockaddr*)&addr, &addr_len);
    (void)mask;
    printf("Accepted connection from %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
    aeCreateFileEvent(eventLoop, sock, AE_READABLE, data_ready, list);
    // book keeping
    list_add_tail(list, &(new_connection(&addr, sock)->link));
}

void test_ae(void)
{
    aeEventLoop* loop = aeCreateEventLoop();
    LIST_T client_list;
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    int rc = 0;
    // bind and listen
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(32000);
    rc = bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr));
    EXPECT(rc == 0);

    rc = listen(sock,1024);
    EXPECT(rc == 0);

    list_init(&client_list);
    aeCreateFileEvent(loop, sock, AE_READABLE, accept_ready, &client_list);
    // the ae timer is not repeated
    printf("Listening on port 32000. The program will exit in 20 seconds\n");
    aeCreateTimeEvent(loop, 1000, on_timer, (void*)1, NULL);
    aeCreateTimeEvent(loop, 20000, on_timer, (void*)2, NULL);

    aeMain(loop);

    aeDeleteFileEvent(loop, sock, AE_READABLE);
    while (client_list.size > 0)
    {
        SOCK_CONNECTION_T* cnxn = CONTAINER_PTR(SOCK_CONNECTION_T, link, client_list.head);
        list_remove_head(&client_list);
        aeDeleteFileEvent(loop, cnxn->sock, AE_READABLE);
        close(cnxn->sock);
        DELETE(cnxn);
        printf("Releasing fd %d\n", cnxn->sock);
    }
    aeDeleteEventLoop(loop);
    close(sock);
    printf("ae test finished\n");
}
